package oneD.fem.algorithm;

import inf.jlinalg.Array2DMatrix;
import inf.jlinalg.IMatrix;

public class Tensor4 implements ITensor {

	double[][][][] A;

	public Tensor4(int n) {
		setIdentity(n);
	}

	public Tensor4(double[][][][] A) {
		this.A = A;
	}

	@Override
	public double get(int... I) {
		return A[I[0]][I[1]][I[2]][I[3]];
	}

	public double[][][][] getA() {
		return A;
	}

	@Override
	public Object clone() {
		Tensor4 theClone = new Tensor4(A.clone());
		return theClone;
	}

	@Override
	public double trace() {
		/*
		 * TODO check the definition of trace of an 4th order tensor
		 */
		double tmp = 0;
		for (int i = 0; i < dimension(); i++)
			for (int j = 0; j < dimension(); j++)
				tmp += get(i, j, i, j);
		return tmp;
	}

	@Override
	public int rank() {
		return 4;
	}

	@Override
	public int dimension() {
		return A.length;
	}

	private boolean checkCompatible(ITensor B, int rank) {
		if (B.rank() != rank) {
			System.out.println("tensor rank is not compatible");
			return false;
		}
		if (B.dimension() != this.dimension()) {
			System.out.println("tensor dimension is not compatible");
			return false;
		}
		return true;
	}

	@Override
	public ITensor contraction(ITensor B) {
		if (B.rank() == 1) {
			// TODO
		} else if (B.rank() == 2) {
			if (!checkCompatible(B, 2))
				return null;
			else {
				IMatrix C = new Array2DMatrix(A.length, A[0].length);
				for (int i = 0; i < A.length; i++)
					for (int j = 0; j < A[i].length; j++) {
						double tmp = 0;
						for (int k = 0; k < A[i][j].length; k++)
							for (int l = 0; l < A[i][j][k].length; l++)
								tmp += A[i][j][k][l] * B.get(k, l);
						C.set(i, j, tmp);
					}
				return new Tensor2(C);
			}
		} else if (B.rank() == 3) {
			// TODO
			return null;
		}
		return null;
	}

	@Override
	public ITensor multiply(ITensor B) {
		if (!checkCompatible(B, this.rank()))
			return null;
		else {
			double[][][][] C = A.clone();
			for (int i = 0; i < C.length; i++)
				for (int j = 0; j < C[i].length; j++)
					for (int k = 0; k < C[i][j].length; k++)
						for (int l = 0; l < C[i][j][k].length; l++)
							C[i][j][k][l] *= B.get(i, j, k, l);
			return new Tensor4(C);
		}
	}

	@Override
	public double dot(ITensor B) {
		if (!checkCompatible(B, this.rank()))
			return 0;
		else {
			double tmp = 0;
			for (int i = 0; i < A.length; i++)
				for (int j = 0; j < A[i].length; j++)
					for (int k = 0; k < A[i][j].length; k++)
						for (int l = 0; l < A[i][j][k].length; l++)
							tmp += get(i, j, k, l) * B.get(i, j, k, l);
			return tmp;
		}
	}

	@Override
	public ITensor product(ITensor B) {
		// TODO
		return null;
	}

	public final static double KroneckerDelta(int i, int j) {
		return (i == j ? 1 : 0);
	}

	private void setIdentity(int n) {
		A = new double[n][][][];
		for (int i = 0; i < n; i++) {
			A[i] = new double[n][][];
			for (int j = 0; j < n; j++) {
				A[i][j] = new double[n][];
				for (int k = 0; k < n; k++)
					A[i][j][k] = new double[n];
			}
		}
		for (int i = 0; i < n; i++)
			for (int j = 0; j < n; j++)
				for (int k = 0; k < n; k++)
					for (int l = 0; l < n; l++)
						A[i][j][k][l] = (KroneckerDelta(i, k)
								* KroneckerDelta(j, l) + KroneckerDelta(i, l)
								* KroneckerDelta(j, k)) / 2;
	}

	public void scale(double s) {
		int n = dimension();
		for (int i = 0; i < n; i++)
			for (int j = 0; j < n; j++)
				for (int k = 0; k < n; k++)
					for (int l = 0; l < n; l++)
						A[i][j][k][l] *= s;
	}

	@Override
	public Tensor4 add(double alpha, ITensor B) {
		double[][][][] tmp = A.clone();
		int n = dimension();
		for (int i = 0; i < n; i++)
			for (int j = 0; j < n; j++)
				for (int k = 0; k < n; k++)
					for (int l = 0; l < n; l++)
						tmp[i][j][k][l] += alpha * B.get(i, j, k, l);
		return new Tensor4(tmp);
	}

	public void print() {
		int n = dimension();
		for (int i = 0; i < n; i++)
			for (int j = 0; j < n; j++)
				for (int k = 0; k < n; k++)
					for (int l = 0; l < n; l++)
						System.out.println("A[" + i + "," + j + "," + k + ","
								+ l + "]=" + A[i][j][k][l]);
	}
}
